﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emico.Data.Repository;

namespace Emico.Data.Entity.Repository
{
    public abstract class DbRepository<TEntity> : DbRepositoryBase<TEntity>
        where TEntity : class
    {
        private DbSet<TEntity> _set;
        public DbRepository(IDbContextWrapper wrapper)
            : base(wrapper)
        {
            this._set = this.Wrapper.Set<TEntity>();
        }

        #region DbRepositoryBase<T, C>

        public override TEntity Attach(TEntity entity)
        {
            return this._set.Attach(entity);
        }

        public override TEntity Add(TEntity entity)
        {
            return this._set.Add(entity);
        }

        public override IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
        {
            return this._set.AddRange(entities);
        }

        public override TEntity Create()
        {
            return this._set.Create();
        }

        public override TEntity Remove(TEntity entity)
        {
            return this._set.Remove(entity);
        }

        public override IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities)
        {
            return this._set.RemoveRange(entities);
        }

        public override IQueryable<TEntity> AsQueryable()
        {
            return this._set;
        }

        public override TEntity Find(params object[] keyValues)
        {
            return this._set.Find(keyValues);
        }

        public override Task<TEntity> FindAsync(params object[] keyValues)
        {
            return this._set.FindAsync(keyValues);
        }

        public override Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
        {
            return this._set.FindAsync(keyValues);
        }

        #endregion
    }

    public abstract class DbRepository<TEntity, TBase> : DbRepositoryBase<TEntity>
        where TBase : class
        where TEntity : class, TBase
    {
        private DbSet<TBase> _set;
        public DbRepository(IDbContextWrapper wrapper)
            : base(wrapper)
        {
            this._set = this.Wrapper.Set<TBase>();
        }

        #region DbRepositoryBase<T>

        public override IQueryable<TEntity> AsQueryable()
        {
            return this._set.OfType<TEntity>();
        }

        public override TEntity Find(params object[] keyValues)
        {
            TBase value = this._set.Find(keyValues);
            return FindResult(value);
        }

        public override Task<TEntity> FindAsync(params object[] keyValues)
        {
            Task<TEntity> task = this._set.FindAsync(keyValues).ContinueWith<TEntity>(t =>
            {
                return FindResult(t.Result);
            });

            return task;
        }

        public override Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
        {
            Task<TEntity> task = this._set.FindAsync(cancellationToken, keyValues).ContinueWith<TEntity>(t =>
            {
                return FindResult(t.Result);
            });

            return task;
        }

        private TEntity FindResult(TBase value)
        {
            if (value is TEntity)
            {
                return (TEntity)value;
            }
            else
            {
                return null;
            }
        }

        #endregion

        public override TEntity Attach(TEntity entity)
        {
            return (TEntity)this._set.Attach(entity);
        }

        public override TEntity Add(TEntity entity)
        {
            return (TEntity)this._set.Add(entity);
        }

        public override IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
        {
            return this._set.AddRange(entities).Cast<TEntity>();
        }

        public override TEntity Create()
        {
            return this._set.Create<TEntity>();
        }

        public override TEntity Remove(TEntity entity)
        {
            return (TEntity)this._set.Remove(entity);
        }

        public override IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities)
        {
            return this._set.RemoveRange(entities).Cast<TEntity>();
        }
    }

    public abstract class DbRepositoryBase<TEntity> : IDbRepository<TEntity>
        where TEntity : class
    {
        protected IDbContextWrapper Wrapper
        {
            get;
            private set;
        }
        public DbRepositoryBase(IDbContextWrapper wrapper)
        {
            if (wrapper == null)
                throw new ArgumentNullException("wrapper");

            this.Wrapper = wrapper;
        }

        #region IRepository<T>
                        
        public abstract TEntity Attach(TEntity entity);
        public abstract TEntity Add(TEntity entity);
        public abstract IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities);
        public abstract TEntity Create();
        public abstract TEntity Remove(TEntity entity);
        public abstract IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities);
        
        public abstract IQueryable<TEntity> AsQueryable();
        public abstract TEntity Find(params object[] keyValues);
        public abstract Task<TEntity> FindAsync(params object[] keyValues);
        public abstract Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues);
        
        #endregion
    }
}
